import { fileURLToPath, URL } from 'node:url';

import uploadSourcemap from '@erdai/vite-plugin-sourcemap-upload';
import terser from '@rollup/plugin-terser';
import react from '@vitejs/plugin-react';
import externalGlobals from 'rollup-plugin-external-globals';
import { visualizer } from 'rollup-plugin-visualizer';
import AutoImport from 'unplugin-auto-import/vite';
import type { ConfigEnv, UserConfig } from 'vite';
import { defineConfig, loadEnv } from 'vite';
import viteCompression from 'vite-plugin-compression';
import { createHtmlPlugin } from 'vite-plugin-html';
import { viteMockServe } from 'vite-plugin-mock';

/**
 * 将一个相对于当前模块文件的路径（relativePath）转换为一个绝对路径。
 *
 * @param relativePath 相对路径
 * @returns 绝对路径
 */
function resolvePath(relativePath: string) {
    return fileURLToPath(new URL(relativePath, import.meta.url));
}

export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
    // 获取项目根目录
    const root = process.cwd();
    // 读取环境变量配置文件，获取环境变量
    const env = loadEnv(mode, root);
    // 获取环境变量配置项
    const { VITE_APP_USE_MOCK, VITE_APP_TITLE } = env;

    return {
        // 服务根目录
        root,
        // 配置路径别名
        resolve: {
            alias: {
                // 使用fileURLToPath和new URL结合import.meta.url更推荐，在ES模块环境中推荐使用
                '@': resolvePath('./src'),
                '#': resolvePath('./types')
            }
        },
        css: {
            // 预处理器配置项
            preprocessorOptions: {
                // 处理less:注意，如果添加是实际的样式而不仅仅是变量，那这些样式在最终的产物中会重复
                less: {
                    // eslint-disable-next-line quotes
                    additionalData: `@import url('@/styles/variable.less');`
                }
            }
        },
        // 插件
        plugins: [
            react(),
            // 自动引入插件
            AutoImport({
                // 自动导入 react-router、react-router-dom相关函数
                imports: ['react', 'react-router-dom', 'ahooks'],
                // 对于自动引入的框架，开启eslint支持, 自动生成.eslintrc-auto-import.json文件，需要在.eslintrc.js中extends配置项中引入
                eslintrc: {
                    enabled: true,
                    filepath: './.eslintrc-auto-import.json'
                },
                // 配置生成.d.ts的生成目录
                dts: resolvePath('./types/auto-imports.d.ts')
            }),
            // 自定义生成html插件，用于注入外部脚本标签等操作
            createHtmlPlugin({
                // 注入外部脚本标签,在index.html模板文件中需配置ejs语法，例如：<%= jsPDFScript %>
                inject: {
                    data: {
                        title: VITE_APP_TITLE || 'admin后台管理系统模板',
                        jsPDFScript:
                            '<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>'
                        // ...其他外链脚本标签...
                    }
                }
            }),
            // 最终产物代码压缩插件
            viteCompression({
                // 体积大于20kb的文件会被压缩
                threshold: 1024 * 20,
                // 压缩文件后缀，默认为 .gz
                ext: '.gz',
                // 压缩算法，默认为 'gzip',可选 'gzip' | 'brotliCompress' | 'deflate' | 'deflateRaw'
                algorithm: 'gzip'
            }),
            // 接口mock插件
            viteMockServe({
                // 是否开启 mock 服务
                enable: VITE_APP_USE_MOCK === 'true',
                // 存放mock文件的文件夹
                mockPath: 'mock'
            }),
            // 打包后上传sourcemap文件到服务器,便于监控平台定位问题
            uploadSourcemap({
                uploadUrl: 'https://your-upload-url.com/upload',
                // 仅在生产环境启用,默认为false
                enable: mode === 'production'
            })
        ],
        // 开发环境配置
        server: {
            // 指定服务器应该监听哪个 IP 地址。 如果将此设置为 0.0.0.0 或者 true 将监听所有地址，包括局域网和公网地址。
            host: 'localhost',
            // 开发环境预览服务器端口
            port: 9000,
            // 启动后是否自动打开浏览器
            open: true,
            // 是否开启CORS跨域
            cors: true,
            // 代理服务器
            proxy: {
                // 以/api开头发送的请求都会被转发到 后端真实地址
                [env.VITE_APP_API_BASEURL]: {
                    // 开发时，替换为对应后端的IP地址，此处使用Apifox的云端mock服务
                    target: 'https://apifoxmock.com/m1/5961994-5650132-default',
                    // 改变 Host Header,允许跨域请求携带cookies
                    changeOrigin: true,
                    // 发起请求时将 '/api' 替换为 ''
                    rewrite: (path) => path.replace(/^\/api/, '')
                },
                // 以/mock/api开头发送的请求都会被转发到 mock服务器地址
                [env.VITE_APP_MOCK_API_BASEURL]: {
                    target: 'http://localhost:9000',
                    // 改变 Host Header,允许跨域请求携带cookies
                    changeOrigin: true
                }
            }
        },
        // 生产环境配置
        build: {
            // 生成source map文件，便于调试源代码，bundle 文件相应的注释将不保留。
            sourcemap: 'hidden',
            // 生成源文件和构建文件的映射关系文件
            manifest: true,
            // 使用terser压缩代码，(注意默认是esbuild，比terser快 20-40 倍)
            minify: 'terser',
            // terser压缩配置
            terserOptions: {
                compress: {
                    // 删除console.log等调试语句
                    drop_console: true,
                    // 删除debugger语句
                    drop_debugger: true,
                    // 删除无用代码（如未使用的变量）
                    dead_code: true,
                    // 提升变量声明
                    hoist_vars: true,
                    // 消除内联函数和循环内的重复计算
                    inline: 2,
                    // 移除不必要的圆括号
                    collapse_vars: true
                },
                output: {
                    // 最大化压缩数字与字符串字面量
                    ascii_only: false,
                    // 移除所有的注释，包括版权信息
                    comments: false,
                    // 控制是否保留引号，true表示尽可能转换为单引号
                    quote_style: 1
                }
            },
            // 警告单个文件体积超过500kb时，会显示警告信息
            chunkSizeWarningLimit: 500,
            // rollup打包配置
            rollupOptions: {
                // 外部依赖配置
                external: ['jsPDF'],
                // 插件配置
                plugins: [
                    // 打包分析插件
                    visualizer({
                        // 生成的分析文件名称
                        filename: 'stats.html',
                        // 是否在生成后自动打开浏览器
                        open: true,
                        // 可视化模板，可选 'sunburst', 'network', 'treemap', 'raw-data'
                        template: 'treemap',
                        // 是否显示 gzip 压缩后的大小
                        gzipSize: true,
                        // 是否显示 brotli 压缩后的大小
                        brotliSize: true
                    }),
                    // terser压缩插件
                    terser(),
                    /**
                        1、需要排除的模块，不打入到最终的bundle中，利用外链引入, 例如jspdf等工具库，可以减少打包体积
                        2、build.rollupOptions.external: ['jspdf']要相应配置
                        3、外链的模块，可利用CDN链接，例如jspdf: <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
                        4、使用时，jspdf为全局变量，可直接使用，或者window.jspdf使用
                        5、类型报错时，可在types/global.d.ts中声明全局变量类型 jsPDF
                    */
                    externalGlobals({
                        jsPDF: 'jsPDF'
                        // ...按需增加需要外链的工具库
                    })
                ],
                // 实验性配置: 启用实验性警告，以记录可能的副作用。
                // experimentalLogSideEffects: true,
                // 输出配置
                output: {
                    /**
                     * 业务打包合并，将小于20kb的模块合并到一起，减少请求数量(注意：只有在不改变加载任何条目时执行的副作用的情况下，才会执行合并)
                     */
                    // experimentalMinChunkSize: 20 * 1024,
                    /* 
                        1.业务代码通过React.lazy加载，此处不做手动分割
                        2.非业务代码手动分割，此处将第三方库单独打包，便于缓存
                     */
                    manualChunks: {
                        'react-vendor': ['react', 'react-dom', 'react-router-dom'],
                        'antd-vendor': ['antd'],
                        'axios-vendor': ['axios'],
                        'echarts-vendor': ['echarts']
                    },
                    /**
                     * 打包后的文件名配置，此处使用hash值作为文件名的一部分，便于缓存
                     * 设置前置文件夹，可利用nginx配置缓存策略
                     */
                    chunkFileNames: 'static/js/[name]-[hash].js',
                    entryFileNames: 'static/js/entry-[hash].js',
                    assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
                }
            }
        }
    };
});
